package com.sx.core.page;

import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;


/**
 * Created by lihao on 2017/3/27.
 *
 * @author   <a href="mailto:lihao@ozstrategy.com">Hao Li</a>
 * @version  03/27/2017 17:08
 */
public class Sort implements Iterable<Sort.Order>, Serializable {
  //~ Static fields/initializers ---------------------------------------------------------------------------------------

  /** TODO: DOCUMENT ME! */
  public static final Sort.Direction DEFAULT_DIRECTION = Sort.Direction.ASC;

  //~ Enums ------------------------------------------------------------------------------------------------------------

  /**
   * TODO: DOCUMENT ME!
   *
   * @author   <a href="mailto:lihao@ozstrategy.com">Hao Li</a>
   * @version  03/27/2017 17:08
   */
  public static enum Direction {
    //~ Enum constants -------------------------------------------------------------------------------------------------

    ASC, DESC;

    //~ Methods --------------------------------------------------------------------------------------------------------

    /**
     * fromString.
     *
     * @param   value  String
     *
     * @return  Sort.Direction
     *
     * @throws  IllegalArgumentException  exception
     */
    public static Sort.Direction fromString(String value) {
      try {
        return Sort.Direction.valueOf(value.toUpperCase(Locale.US));
      } catch (Exception e) {
        throw new IllegalArgumentException(String.format(
            "Invalid value '%s' for orders given! Has to be either 'desc' or 'asc' (case insensitive).", value), e);
      }
    }

    //~ ----------------------------------------------------------------------------------------------------------------

    /**
     * fromStringOrNull.
     *
     * @param   value  String
     *
     * @return  Sort.Direction
     */
    public static Sort.Direction fromStringOrNull(String value) {
      try {
        return fromString(value);
      } catch (IllegalArgumentException e) {
        return null;
      }
    }
  }

  //~ Instance fields --------------------------------------------------------------------------------------------------

  private final List<Order> orders;

  //~ Constructors -----------------------------------------------------------------------------------------------------

  /**
   * Creates a new Sort object.
   *
   * @param  orders  Sort.Order
   */
  public Sort(Sort.Order... orders) {
    this(Arrays.asList(orders));
  }

  /**
   * Creates a new Sort object.
   *
   * @param   orders  List
   *
   * @throws  IllegalArgumentException  exception
   */
  public Sort(List<Order> orders) {
    if ((null == orders) || orders.isEmpty()) {
      throw new IllegalArgumentException("You have to provide at least one sort property to sort by!");
    }

    this.orders = orders;
  }

  /**
   * Creates a new Sort object.
   *
   * @param  properties  String
   */
  public Sort(String... properties) {
    this(DEFAULT_DIRECTION, properties);
  }

  /**
   * Creates a new Sort object.
   *
   * @param  direction   Sort.Direction
   * @param  properties  String
   */
  public Sort(Sort.Direction direction, String... properties) {
    this(direction, (properties == null) ? new ArrayList<String>() : Arrays.asList(properties));
  }

  /**
   * Creates a new Sort object.
   *
   * @param   direction   Sort.Direction
   * @param   properties  List
   *
   * @throws  IllegalArgumentException  exception
   */
  public Sort(Sort.Direction direction, List<String> properties) {
    if ((properties == null) || properties.isEmpty()) {
      throw new IllegalArgumentException("You have to provide at least one property to sort by!");
    }

    this.orders = new ArrayList<Order>(properties.size());

    for (String property : properties) {
      this.orders.add(new Sort.Order(direction, property));
    }
  }

  //~ Methods ----------------------------------------------------------------------------------------------------------

  /**
   * and.
   *
   * @param   sort  Sort
   *
   * @return  Sort
   */
  public Sort and(Sort sort) {
    if (sort == null) {
      return this;
    }

    ArrayList<Order> these = new ArrayList<Order>(this.orders);

    for (Sort.Order order : sort) {
      these.add(order);
    }

    return new Sort(these);
  }

  //~ ------------------------------------------------------------------------------------------------------------------

  /**
   * @see  Object#equals(Object)
   */
  @Override public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }

    if (!(obj instanceof Sort)) {
      return false;
    }

    Sort that = (Sort) obj;

    return this.orders.equals(that.orders);
  }

  //~ ------------------------------------------------------------------------------------------------------------------

  /**
   * getter method for order for.
   *
   * @param   property  String
   *
   * @return  Sort.Order
   */
  public Sort.Order getOrderFor(String property) {
    for (Sort.Order order : this) {
      if (order.getProperty().equals(property)) {
        return order;
      }
    }

    return null;
  }

  //~ ------------------------------------------------------------------------------------------------------------------

  /**
   * @see  Object#hashCode()
   */
  @Override public int hashCode() {
    int result = 17;
    result = (31 * result) + orders.hashCode();

    return result;
  }

  //~ ------------------------------------------------------------------------------------------------------------------

  /**
   * @see  Iterable#iterator()
   */
  @Override public Iterator<Order> iterator() {
    return this.orders.iterator();
  }

  //~ ------------------------------------------------------------------------------------------------------------------

  /**
   * @see  Object#toString()
   */
  @Override public String toString() {
    return StringUtils.collectionToCommaDelimitedString(orders);
  }

  //~ Inner Classes ----------------------------------------------------------------------------------------------------

  public static class Order implements Serializable {
    //~ Static fields/initializers -------------------------------------------------------------------------------------

    private static final long serialVersionUID = 1522511010900108987L;

    //~ Instance fields ------------------------------------------------------------------------------------------------

    private final Sort.Direction direction;
    private final String         property;

    //~ Constructors ---------------------------------------------------------------------------------------------------

    public Order(String property) {
      this(DEFAULT_DIRECTION, property);
    }

    private Order(Sort.Direction direction, String property) {
      if (!StringUtils.hasText(property)) {
        throw new IllegalArgumentException("Property must not null or empty!");
      }

      this.direction = (direction == null) ? DEFAULT_DIRECTION : direction;
      this.property  = property;
    }

    //~ Methods --------------------------------------------------------------------------------------------------------

    @Override public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }

      if (!(obj instanceof Sort.Order)) {
        return false;
      }

      Sort.Order that = (Sort.Order) obj;

      return this.direction.equals(that.direction) && this.property.equals(that.property);
    }

    //~ ----------------------------------------------------------------------------------------------------------------

    public Sort.Direction getDirection() {
      return direction;
    }

    //~ ----------------------------------------------------------------------------------------------------------------

    public String getProperty() {
      return property;
    }

    //~ ----------------------------------------------------------------------------------------------------------------

    @Override public int hashCode() {
      int result = 17;

      result = (31 * result) + direction.hashCode();
      result = (31 * result) + property.hashCode();

      return result;
    }

    //~ ----------------------------------------------------------------------------------------------------------------

    public boolean isAscending() {
      return this.direction.equals(Sort.Direction.ASC);
    }

    //~ ----------------------------------------------------------------------------------------------------------------

    @Override public String toString() {
      String result = String.format("%s: %s", property, direction);

      return result;
    }

    //~ ----------------------------------------------------------------------------------------------------------------

    public Sort.Order with(Sort.Direction order) {
      return new Sort.Order(order, this.property);
    }

    //~ ----------------------------------------------------------------------------------------------------------------

    public Sort withProperties(String... properties) {
      return new Sort(this.direction, properties);
    }
  } // end class Order
} // end class Sort
